package com.atguigu.tingshu.account.service.impl;
import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.google.common.collect.Maps;

import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.MqOpsService;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.user.UserInfo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 处理消息的service
 */
@Service
public class MqOpsServiceImpl implements MqOpsService {


    @Autowired
    private UserAccountMapper userAccountMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserAccountService userAccountService;

    @Override
    public int initAccount(Long userId) {

        //1.构建实体类对象
        UserAccount userAccount = new UserAccount();
        //2.设置属性
        userAccount.setUserId(userId);
        userAccount.setTotalAmount(new BigDecimal("0"));
        userAccount.setLockAmount(new BigDecimal("0"));
        userAccount.setAvailableAmount(new BigDecimal("0"));
        userAccount.setTotalIncomeAmount(new BigDecimal("0"));
        userAccount.setTotalPayAmount(new BigDecimal("0"));
        userAccount.setId(0L);
        userAccount.setCreateTime(new Date());
        userAccount.setUpdateTime(new Date());
        userAccount.setIsDeleted(0);
        userAccount.setParam(Maps.newHashMap());

        //3.保存
        int insert = userAccountMapper.insert(userAccount);


        return insert;
    }


    /**
     * 扣减用户余额
     *
     * userAccountMinus要保证幂等性(因为上游可能会对同一条消息发送多次,因此这个消费者就可能从消息队列中取出多一样的消息,消费多次,会导致数据不一致)
     * 幂等性的实现:
     * 1):锁机制:分布式锁(未来这个实例可能有多个)
     * 2):redis+token机制
     * 3):数据库层面控制(唯一索引+防重表)
     * order_info
     * id  order_no(唯一索引)  ......
     * <p>
     * <p>
     * order_info_retry(防重表)
     * id  order_no（普通索引）....
     * <p>
     * 流程：像order_info表保存数据的时候 额外的像防重表中也插入一条数据
     * order_no:1001
     * @param orderNo
     * @Transactional void  saveOrderInfo(){
     * // 1.order_info
     * 1  1001
     * 2  1001
     * <p>
     * // 2.order_info_retry
     * 1 1001
     * 2 1001
     * <p>
     * }
     * @param orderNo
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void userAccountMinus(String orderNo) {

        //0.定义一个锁定记录的缓存key
        String dateKey = "user:account:result:"+orderNo;
        String lockKey = "user:account:minus:lock:"+orderNo;
        String retryConsumerKey = "user:account:retry:"+orderNo;


        //利用redis+token机制保证幂等性
        String retryMsgFlag = redisTemplate.opsForValue().get(retryConsumerKey);
        if (!StringUtils.isEmpty(retryMsgFlag)){
            return; //有记录,直接返回
        }


        Boolean aboolean=redisTemplate.opsForValue().setIfAbsent(lockKey, "c", 30, TimeUnit.MINUTES);
         if (!aboolean){
             return;//没有拿到锁的,直接返回
         }

        //1.从Redis中获取锁定记录
        String accountLockStr = redisTemplate.opsForValue().get(dateKey);
        if (StringUtils.isEmpty(accountLockStr)){
            return;
        }
        //解析数据
        AccountLockResultVo accountLockResultVo = JSONObject.parseObject(accountLockStr, AccountLockResultVo.class);
        Long userId = accountLockResultVo.getUserId();
        BigDecimal amount = accountLockResultVo.getAmount();
        String content = accountLockResultVo.getContent();

        try {
            //2.根据锁定的内容修改user_account
            int i = userAccountMapper.userAccountMinus(userId, amount, content);
            //3.根据锁定的内容插入user_account_detail流水
            userAccountService.log(userId, "消费"+content,"1204",amount,orderNo);
            redisTemplate.delete(dateKey); //删除缓存中锁定结果的记录(业务不用了)
            redisTemplate.opsForValue().setIfAbsent(retryConsumerKey, "c"); //存一个占位符
        } catch (Exception e) {
            redisTemplate.delete(lockKey);
            throw new GuiguException(201,"消费者内部出现了异常");
        }

    }


    /**
     * 解锁和恢复余额
     * @param orderNo
     */
    @Override
    public void userAccountUnLock(String orderNo) {

        //0.定义一个锁定记录的缓存key
        String dateKey = "user:account:result:"+orderNo;
        String lockKey = "user:account:unlock:lock:"+orderNo;
        String retryConsumerKey = "user:account:unlock:retry:"+orderNo;


        //利用redis+token机制保证幂等性
        String retryMsgFlag = redisTemplate.opsForValue().get(retryConsumerKey);
        if (!StringUtils.isEmpty(retryMsgFlag)){
            return; //有记录,直接返回
        }


        Boolean aboolean=redisTemplate.opsForValue().setIfAbsent(lockKey, "c", 30, TimeUnit.MINUTES);
        if (!aboolean){
            return;//没有拿到锁的,直接返回
        }

        //1.从Redis中获取锁定记录
        String accountLockStr = redisTemplate.opsForValue().get(dateKey);
        if (StringUtils.isEmpty(accountLockStr)){
            return;
        }
        //解析数据
        AccountLockResultVo accountLockResultVo = JSONObject.parseObject(accountLockStr, AccountLockResultVo.class);
        Long userId = accountLockResultVo.getUserId();
        BigDecimal amount = accountLockResultVo.getAmount();
        String content = accountLockResultVo.getContent();

        try {
            //2.根据锁定的内容修改user_account
            int i = userAccountMapper.userAccountUnLock(userId, amount, content);
            //3.根据锁定的内容插入user_account_detail流水
            userAccountService.log(userId, "解锁"+content,"1204",amount,orderNo);
            redisTemplate.delete(dateKey); //删除缓存中锁定结果的记录(业务不用了)
            redisTemplate.opsForValue().setIfAbsent(retryConsumerKey, "c"); //存一个占位符
        } catch (Exception e) {
            redisTemplate.delete(lockKey);
            throw new GuiguException(201,"消费者内部出现了异常");
        }

    }
}
